using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

using NBusiness.CodeDom;
using NBusiness.CodeDom.Compiler;
using Microsoft.VisualBasic;
using System.Security.Policy;
using System.Security;
using System.Security.Permissions;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting;
using NBusiness.ESharp.Compiler.Proxy;
using NBusiness.CodeDom.Services;

namespace NBusiness.ESharp.Compiler
{
    /// <summary>
    /// Compiles entities into a lower level programming language.
    /// </summary>
    /// <remarks>Be sure to dispose this object since compilation is done is a separate AppDomain.</remarks>
    [Serializable]
    internal class ESharpCompiler : EntityCompiler
    {
        AppDomain _domain = null;
        ESharpCompilerProxy _proxy = null;

        ESharpProvider _provider;
        IBuildErrorService _errorService;

        public ESharpCompiler(ESharpProvider provider)
        {
            _provider = provider;
            _errorService = _provider.GetService(typeof(IBuildErrorService)) as IBuildErrorService;
        }

        public override EntityFile[] CompileRoot<T>(EntityRoot root, EntityCompileParameters parameters)
        {
            List<EntityFile> files = new List<EntityFile>();

            foreach (EntityFamily family in root.Families.Values)
            {
                EntityFile[] results = CompileFamily<T>(family, parameters);
                files.AddRange(results);
            }

            return files.ToArray();
        }

        private EntityFile[] CompileFamily<T>(EntityFamily family, EntityCompileParameters parameters) 
            where T : Attribute
        {
            List<EntityFile> files = new List<EntityFile>();

            foreach (EntityFamily subFamily in family.Families.Values)
            {
                EntityFile[] results = CompileFamily<T>(subFamily, parameters);
                files.AddRange(results);
            }

            foreach (Entity entity in family.Entities)
            {
                EntityFile[] results = CompileEntity<T>(entity, parameters);
                files.AddRange(results);
            }

            return files.ToArray();
        }

        public override EntityFile[] CompileEntity<T>(Entity entity, EntityCompileParameters parameters)
        {
            if (_domain == null)
            {
                AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
                
                string location = Path.GetDirectoryName(typeof(ESharpCompilerProxy).Assembly.Location);
                AppDomainSetup setup = new AppDomainSetup 
                {
                    ApplicationBase = location
                };

                _domain = AppDomain.CreateDomain("ESharp Compiler", null, setup);

                object proxy = _domain.CreateInstanceAndUnwrap(
                    typeof(ESharpCompilerProxy).Assembly.FullName,
                    typeof(ESharpCompilerProxy).FullName);

                _proxy = proxy as ESharpCompilerProxy;
            }
            
            _proxy.CompileEntity<T>(entity, parameters);

            string[] filenames = _proxy.GetFileNames();
            List<EntityFile> files = new List<EntityFile>();
            foreach (string filename in filenames)
            {
                string data = _proxy.GetFileData(filename);
                files.Add(new EntityFile { FileName = filename, Data = data });
            }

            return files.ToArray();
        }


        #region Error Handling

        void proxy_BuildError(object sender, EntityBuildErrorEventArgs e)
        {
            if (_errorService != null)
                _errorService.Add(e.Error);
        }

        #endregion

        Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            Assembly found = null;
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (assembly.FullName == args.Name)
                {
                    found = assembly;
                    break;
                }
            }
            return found;
        }

        public override void Dispose()
        {                        
            AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            if (_domain != null)
            {
                _domain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);
                AppDomain.Unload(_domain);
            }
            base.Dispose();
        }
    }
}
